//+-------------------------------------------------------------------------------+
//| Copyright (c) 2003 Liping Dai. All rights reserved.                           |
//| Web: www.lipingshare.com                                                      |
//| Email: lipingshare@yahoo.com                                                  |
//|                                                                               |
//| Copyright and Permission Details:                                             |
//| =================================                                             |
//| Permission is hereby granted, free of charge, to any person obtaining a copy  |
//| of this software and associated documentation files (the "Software"), to deal |
//| in the Software without restriction, including without limitation the rights  |
//| to use, copy, modify, merge, publish, distribute, and/or sell copies of the   |
//| Software, subject to the following conditions:                                |
//|                                                                               |
//| 1. Redistributions of source code must retain the above copyright notice, this|
//| list of conditions and the following disclaimer.                              |
//|                                                                               |
//| 2. Redistributions in binary form must reproduce the above copyright notice,  |
//| this list of conditions and the following disclaimer in the documentation     |
//| and/or other materials provided with the distribution.                        |
//|                                                                               |
//| THE SOFTWARE PRODUCT IS PROVIDED �AS IS� WITHOUT WARRANTY OF ANY KIND,        |
//| EITHER EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED         |
//| WARRANTIES OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR        |
//| A PARTICULAR PURPOSE.                                                         |
//+-------------------------------------------------------------------------------+

using System;
using System.IO;

namespace MultiplayerARPG.MMO
{
    namespace LipingShare.LCLib.Asn1Processor
    {
        /// <summary>
        /// Utility functions.
        /// </summary>
        internal class Asn1Util
        {

            /// <summary>
            /// Check if the string is ASN.1 encoded hex string.
            /// </summary>
            /// <param name="dataStr">The string.</param>
            /// <returns>true:Yes, false:No.</returns>
            public static bool IsAsn1EncodedHexStr(string dataStr)
            {
                bool retval = false;
                try
                {
                    byte[] data = HexStrToBytes(dataStr);
                    if (data.Length > 0)
                    {
                        Asn1Node node = new Asn1Node();
                        retval = node.LoadData(data);
                    }
                }
                catch
                {
                    retval = false;
                }
                return retval;
            }

            /// <summary>
            /// Format a string to have certain line length and character group length.
            /// Sample result FormatString(xstr,32,2):
            /// <code>07 AE 0B E7 84 5A D4 6C 6A BD DF 8F 89 88 9E F1</code>
            /// </summary>
            /// <param name="inStr">source string.</param>
            /// <param name="lineLen">line length.</param>
            /// <param name="groupLen">group length.</param>
            /// <returns></returns>
            public static string FormatString(string inStr, int lineLen, int groupLen)
            {
                char[] tmpCh = new char[inStr.Length * 2];
                int i, c = 0, linec = 0;
                int gc = 0;
                for (i = 0; i < inStr.Length; i++)
                {
                    tmpCh[c++] = inStr[i];
                    gc++;
                    linec++;
                    if (gc >= groupLen && groupLen > 0)
                    {
                        tmpCh[c++] = ' ';
                        gc = 0;
                    }
                    if (linec >= lineLen)
                    {
                        tmpCh[c++] = '\r';
                        tmpCh[c++] = '\n';
                        linec = 0;
                    }
                }
                string retval = new string(tmpCh);
                retval = retval.TrimEnd('\0');
                retval = retval.TrimEnd('\n');
                retval = retval.TrimEnd('\r');
                return retval;
            }

            /// <summary>
            /// Generate a string by duplicating <see cref="char"/> xch.
            /// </summary>
            /// <param name="len">duplicate times.</param>
            /// <param name="xch">the duplicated character.</param>
            /// <returns></returns>
            public static string GenStr(int len, char xch)
            {
                char[] ch = new char[len];
                for (int i = 0; i < len; i++)
                {
                    ch[i] = xch;
                }
                return new string(ch);
            }

            /// <summary>
            /// Convert byte array to a <see cref="long"/> integer.
            /// </summary>
            /// <param name="bytes"></param>
            /// <returns></returns>
            public static long BytesToLong(byte[] bytes)
            {
                long tempInt = 0;
                for (int i = 0; i < bytes.Length; i++)
                {
                    tempInt = tempInt << 8 | bytes[i];
                }
                return tempInt;
            }

            /// <summary>
            /// Convert a ASCII byte array to string, also filter out the null characters.
            /// </summary>
            /// <param name="bytes"></param>
            /// <returns></returns>
            public static string BytesToString(byte[] bytes)
            {
                string retval = "";
                if (bytes == null || bytes.Length < 1) return retval;
                char[] cretval = new char[bytes.Length];
                for (int i = 0, j = 0; i < bytes.Length; i++)
                {
                    if (bytes[i] != '\0')
                    {
                        cretval[j++] = (char)bytes[i];
                    }
                }
                retval = new string(cretval);
                retval = retval.TrimEnd('\0');
                return retval;
            }

            /// <summary>
            /// Convert ASCII string to byte array.
            /// </summary>
            /// <param name="msg"></param>
            /// <returns></returns>
            public static byte[] StringToBytes(string msg)
            {
                byte[] retval = new byte[msg.Length];
                for (int i = 0; i < msg.Length; i++)
                {
                    retval[i] = (byte)msg[i];
                }
                return retval;
            }

            /// <summary>
            /// Compare source and target byte array.
            /// </summary>
            /// <param name="source"></param>
            /// <param name="target"></param>
            /// <returns></returns>
            public static bool IsEqual(byte[] source, byte[] target)
            {
                if (source == null) return false;
                if (target == null) return false;
                if (source.Length != target.Length) return false;
                for (int i = 0; i < source.Length; i++)
                {
                    if (source[i] != target[i]) return false;
                }
                return true;
            }

            /// <summary>
            /// Constant hex digits array.
            /// </summary>
            static char[] hexDigits = { '0', '1', '2', '3', '4', '5', '6', '7',
                                    '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};

            /// <summary>
            /// Convert a byte array to hex string.
            /// </summary>
            /// <param name="bytes">source array.</param>
            /// <returns>hex string.</returns>
            public static string ToHexString(byte[] bytes)
            {
                if (bytes == null) return "";
                char[] chars = new char[bytes.Length * 2];
                int b, i;
                for (i = 0; i < bytes.Length; i++)
                {
                    b = bytes[i];
                    chars[i * 2] = hexDigits[b >> 4];
                    chars[i * 2 + 1] = hexDigits[b & 0xF];
                }
                return new string(chars);
            }

            /// <summary>
            /// Check if the character is a valid hex digits.
            /// </summary>
            /// <param name="ch">source character.</param>
            /// <returns>true:Valid, false:Invalid.</returns>
            public static bool IsValidHexDigits(char ch)
            {
                bool retval = false;
                for (int i = 0; i < hexDigits.Length; i++)
                {
                    if (hexDigits[i] == ch)
                    {
                        retval = true;
                        break;
                    }
                }
                return retval;
            }

            /// <summary>
            /// Get hex digits value.
            /// </summary>
            /// <param name="ch">source character.</param>
            /// <returns>hex digits value.</returns>
            public static byte GetHexDigitsVal(char ch)
            {
                byte retval = 0;
                for (int i = 0; i < hexDigits.Length; i++)
                {
                    if (hexDigits[i] == ch)
                    {
                        retval = (byte)i;
                        break;
                    }
                }
                return retval;
            }

            /// <summary>
            /// Convert hex string to byte array.
            /// </summary>
            /// <param name="hexStr">Source hex string.</param>
            /// <returns>return byte array.</returns>
            public static byte[] HexStrToBytes(string hexStr)
            {
                hexStr = hexStr.Replace(" ", "");
                hexStr = hexStr.Replace("\r", "");
                hexStr = hexStr.Replace("\n", "");
                hexStr = hexStr.ToUpper();
                if ((hexStr.Length % 2) != 0) throw new Exception("Invalid Hex string: odd length.");
                int i;
                for (i = 0; i < hexStr.Length; i++)
                {
                    if (!IsValidHexDigits(hexStr[i]))
                    {
                        throw new Exception("Invalid Hex string: included invalid character [" +
                            hexStr[i] + "]");
                    }
                }
                int bc = hexStr.Length / 2;
                byte[] retval = new byte[bc];
                int b1, b2, b;
                for (i = 0; i < bc; i++)
                {
                    b1 = GetHexDigitsVal(hexStr[i * 2]);
                    b2 = GetHexDigitsVal(hexStr[i * 2 + 1]);
                    b = ((b1 << 4) | b2);
                    retval[i] = (byte)b;
                }
                return retval;
            }

            /// <summary>
            /// Check if the source string is a valid hex string.
            /// </summary>
            /// <param name="hexStr">source string.</param>
            /// <returns>true:Valid, false:Invalid.</returns>
            public static bool IsHexStr(string hexStr)
            {
                byte[] bytes = null;
                try
                {
                    bytes = HexStrToBytes(hexStr);
                }
                catch
                {
                    return false;
                }
                if (bytes == null || bytes.Length < 0)
                {
                    return false;
                }
                else
                {
                    return true;
                }
            }

            private const string PemStartStr = "-----BEGIN";
            private const string PemEndStr = "-----END";
            /// <summary>
            /// Check if the source string is PEM formated string.
            /// </summary>
            /// <param name="pemStr">source string.</param>
            /// <returns>true:Valid, false:Invalid.</returns>
            public static bool IsPemFormated(string pemStr)
            {
                byte[] data = null;
                try
                {
                    data = PemToBytes(pemStr);
                }
                catch
                {
                    return false;
                }
                return (data.Length > 0);
            }

            /// <summary>
            /// Check if a file is PEM formated.
            /// </summary>
            /// <param name="fileName">source file name.</param>
            /// <returns>true:Yes, false:No.</returns>
            public static bool IsPemFormatedFile(string fileName)
            {
                bool retval = false;
                try
                {
                    FileStream fs = new FileStream(fileName, System.IO.FileMode.Open);
                    byte[] data = new byte[fs.Length];
                    fs.Read(data, 0, data.Length);
                    fs.Close();
                    string dataStr = Asn1Util.BytesToString(data);
                    retval = IsPemFormated(dataStr);
                }
                catch
                {
                    retval = false;
                }
                return retval;
            }

            /// <summary>
            /// Convert PEM formated string into <see cref="Stream"/> and set the Stream position to 0.
            /// </summary>
            /// <param name="pemStr">source string.</param>
            /// <returns>output stream.</returns>
            public static Stream PemToStream(string pemStr)
            {
                byte[] bytes = PemToBytes(pemStr);
                MemoryStream retval = new MemoryStream(bytes);
                retval.Position = 0;
                return retval;
            }

            /// <summary>
            /// Convert PEM formated string into byte array.
            /// </summary>
            /// <param name="pemStr">source string.</param>
            /// <returns>output byte array.</returns>
            public static byte[] PemToBytes(string pemStr)
            {
                byte[] retval = null;
                string[] lines = pemStr.Split('\n');
                string base64Str = "";
                bool started = false, ended = false;
                string cline = "";
                for (int i = 0; i < lines.Length; i++)
                {
                    cline = lines[i].ToUpper();
                    if (cline == "") continue;
                    if (cline.Length > PemStartStr.Length)
                    {
                        if (!started && cline.Substring(0, PemStartStr.Length) == PemStartStr)
                        {
                            started = true;
                            continue;
                        }
                    }
                    if (cline.Length > PemEndStr.Length)
                    {
                        if (cline.Substring(0, PemEndStr.Length) == PemEndStr)
                        {
                            ended = true;
                            break;
                        }
                    }
                    if (started)
                    {
                        base64Str += lines[i];
                    }
                }
                if (!(started && ended))
                {
                    throw new Exception("'BEGIN'/'END' line is missing.");
                }
                base64Str = base64Str.Replace("\r", "");
                base64Str = base64Str.Replace("\n", "");
                base64Str = base64Str.Replace("\n", " ");
                retval = Convert.FromBase64String(base64Str);
                return retval;
            }

            /// <summary>
            /// Convert byte array to PEM formated string.
            /// </summary>
            /// <param name="data"></param>
            /// <returns></returns>
            public static string BytesToPem(byte[] data)
            {
                return BytesToPem(data, "");
            }

            /// <summary>
            /// Retrieve PEM file heading.
            /// </summary>
            /// <param name="fileName">source file name.</param>
            /// <returns>heading string.</returns>
            public static string GetPemFileHeader(string fileName)
            {
                try
                {
                    FileStream fs = new FileStream(fileName, FileMode.Open);
                    byte[] data = new byte[fs.Length];
                    fs.Read(data, 0, data.Length);
                    fs.Close();
                    string dataStr = Asn1Util.BytesToString(data);
                    return GetPemHeader(dataStr);
                }
                catch
                {
                    return "";
                }
            }

            /// <summary>
            /// Retrieve PEM heading from a PEM formated string.
            /// </summary>
            /// <param name="pemStr">source string.</param>
            /// <returns>heading string.</returns>
            public static string GetPemHeader(string pemStr)
            {
                string[] lines = pemStr.Split('\n');
                bool started = false;
                string cline = "";
                for (int i = 0; i < lines.Length; i++)
                {
                    cline = lines[i].ToUpper().Replace("\r", "");
                    if (cline == "") continue;
                    if (cline.Length > PemStartStr.Length)
                    {
                        if (!started && cline.Substring(0, PemStartStr.Length) == PemStartStr)
                        {
                            started = true;
                            string retstr = lines[i].Substring(PemStartStr.Length,
                                    lines[i].Length -
                                    PemStartStr.Length).Replace("-----", "");
                            return retstr.Replace("\r", "");
                        }
                    }
                    else
                    {
                        continue;
                    }
                }
                return "";
            }

            /// <summary>
            /// Convert byte array to PEM formated string and set the heading as pemHeader.
            /// </summary>
            /// <param name="data">source array.</param>
            /// <param name="pemHeader">PEM heading.</param>
            /// <returns>PEM formated string.</returns>
            public static string BytesToPem(byte[] data, string pemHeader)
            {
                if (pemHeader == null || pemHeader.Length < 1)
                {
                    pemHeader = "ASN.1 Editor Generated PEM File";
                }
                string retval = "";
                if (pemHeader.Length > 0 && pemHeader[0] != ' ')
                {
                    pemHeader = " " + pemHeader;
                }
                retval = Convert.ToBase64String(data);
                retval = FormatString(retval, 64, 0);
                retval = "-----BEGIN" + pemHeader + "-----\r\n" +
                         retval +
                         "\r\n-----END" + pemHeader + "-----\r\n";
                return retval;
            }

            /// <summary>
            /// Calculate how many bits is enough to hold ivalue.
            /// </summary>
            /// <param name="ivalue">source value.</param>
            /// <returns>bits number.</returns>
            public static int BitPrecision(ulong ivalue)
            {
                if (ivalue == 0) return 0;
                int l = 0, h = 8 * 4; // 4: sizeof(ulong)
                while (h - l > 1)
                {
                    int t = (int)(l + h) / 2;
                    if ((ivalue >> t) != 0)
                        l = t;
                    else
                        h = t;
                }
                return h;
            }

            /// <summary>
            /// Calculate how many bytes is enough to hold the value.
            /// </summary>
            /// <param name="value">input value.</param>
            /// <returns>bytes number.</returns>
            public static int BytePrecision(ulong value)
            {
                int i;
                for (i = 4; i > 0; --i) // 4: sizeof(ulong)
                    if ((value >> (i - 1) * 8) != 0)
                        break;
                return i;
            }

            /// <summary>
            /// ASN.1 DER length encoder.
            /// </summary>
            /// <param name="xdata">result output stream.</param>
            /// <param name="length">source length.</param>
            /// <returns>result bytes.</returns>
            public static int DERLengthEncode(Stream xdata, ulong length)
            {
                int i = 0;
                if (length <= 0x7f)
                {
                    xdata.WriteByte((byte)length);
                    i++;
                }
                else
                {
                    xdata.WriteByte((byte)(BytePrecision(length) | 0x80));
                    i++;
                    for (int j = BytePrecision((ulong)length); j > 0; --j)
                    {
                        xdata.WriteByte((byte)(length >> (j - 1) * 8));
                        i++;
                    }
                }
                return i;
            }

            /// <summary>
            /// ASN.1 DER length decoder.
            /// </summary>
            /// <param name="bt">Source stream.</param>
            /// <param name="isIndefiniteLength">Output parameter.</param>
            /// <returns>Output length.</returns>
            public static long DerLengthDecode(Stream bt, ref bool isIndefiniteLength)
            {
                isIndefiniteLength = false;
                long length = 0;
                byte b;
                b = (byte)bt.ReadByte();
                if ((b & 0x80) == 0)
                {
                    length = b;
                }
                else
                {
                    long lengthBytes = b & 0x7f;
                    if (lengthBytes == 0)
                    {
                        isIndefiniteLength = true;
                        return -2; // Indefinite length.
                    }

                    length = 0;
                    while (lengthBytes-- > 0)
                    {
                        if ((length >> (8 * (4 - 1))) > 0) // 4: sizeof(long)
                        {
                            return -1; // Length overflow.
                        }
                        b = (byte)bt.ReadByte();
                        length = (length << 8) | b;
                    }

                    if (length <= 0x7f)
                    {
                        return -1; // Indicated false node
                    }
                }
                return length;
            }

            /// <summary>
            /// Decode tag value to return tag name.
            /// </summary>
            /// <param name="tag">input tag.</param>
            /// <returns>tag name.</returns>
            static public string GetTagName(byte tag)
            {
                string retval = "";
                if ((tag & Asn1TagClasses.CLASS_MASK) != 0)
                {
                    switch (tag & Asn1TagClasses.CLASS_MASK)
                    {
                        case Asn1TagClasses.CONTEXT_SPECIFIC:
                            retval += "CONTEXT SPECIFIC (" + ((int)(tag & Asn1Tag.TAG_MASK)).ToString() + ")";
                            break;
                        case Asn1TagClasses.APPLICATION:
                            retval += "APPLICATION (" + ((int)(tag & Asn1Tag.TAG_MASK)).ToString() + ")";
                            break;
                        case Asn1TagClasses.PRIVATE:
                            retval += "PRIVATE (" + ((int)(tag & Asn1Tag.TAG_MASK)).ToString() + ")";
                            break;
                        case Asn1TagClasses.CONSTRUCTED:
                            retval += "CONSTRUCTED (" + ((int)(tag & Asn1Tag.TAG_MASK)).ToString() + ")";
                            break;
                        case Asn1TagClasses.UNIVERSAL:
                            retval += "UNIVERSAL (" + ((int)(tag & Asn1Tag.TAG_MASK)).ToString() + ")";
                            break;
                    }
                }
                else
                {
                    switch (tag & Asn1Tag.TAG_MASK)
                    {
                        case Asn1Tag.BOOLEAN:
                            retval += "BOOLEAN";
                            break;
                        case Asn1Tag.INTEGER:
                            retval += "INTEGER";
                            break;
                        case Asn1Tag.BIT_STRING:
                            retval += "BIT STRING";
                            break;
                        case Asn1Tag.OCTET_STRING:
                            retval += "OCTET STRING";
                            break;
                        case Asn1Tag.TAG_NULL:
                            retval += "NULL";
                            break;
                        case Asn1Tag.OBJECT_IDENTIFIER:
                            retval += "OBJECT IDENTIFIER";
                            break;
                        case Asn1Tag.OBJECT_DESCRIPTOR:
                            retval += "OBJECT DESCRIPTOR";
                            break;
                        case Asn1Tag.RELATIVE_OID:
                            retval += "RELATIVE-OID";
                            break;
                        case Asn1Tag.EXTERNAL:
                            retval += "EXTERNAL";
                            break;
                        case Asn1Tag.REAL:
                            retval += "REAL";
                            break;
                        case Asn1Tag.ENUMERATED:
                            retval += "ENUMERATED";
                            break;
                        case Asn1Tag.UTF8_STRING:
                            retval += "UTF8 STRING";
                            break;
                        case (Asn1Tag.SEQUENCE):
                            retval += "SEQUENCE";
                            break;
                        case (Asn1Tag.SET):
                            retval += "SET";
                            break;
                        case Asn1Tag.NUMERIC_STRING:
                            retval += "NUMERIC STRING";
                            break;
                        case Asn1Tag.PRINTABLE_STRING:
                            retval += "PRINTABLE STRING";
                            break;
                        case Asn1Tag.T61_STRING:
                            retval += "T61 STRING";
                            break;
                        case Asn1Tag.VIDEOTEXT_STRING:
                            retval += "VIDEOTEXT STRING";
                            break;
                        case Asn1Tag.IA5_STRING:
                            retval += "IA5 STRING";
                            break;
                        case Asn1Tag.UTC_TIME:
                            retval += "UTC TIME";
                            break;
                        case Asn1Tag.GENERALIZED_TIME:
                            retval += "GENERALIZED TIME";
                            break;
                        case Asn1Tag.GRAPHIC_STRING:
                            retval += "GRAPHIC STRING";
                            break;
                        case Asn1Tag.VISIBLE_STRING:
                            retval += "VISIBLE STRING";
                            break;
                        case Asn1Tag.GENERAL_STRING:
                            retval += "GENERAL STRING";
                            break;
                        case Asn1Tag.UNIVERSAL_STRING:
                            retval += "UNIVERSAL STRING";
                            break;
                        case Asn1Tag.BMPSTRING:
                            retval += "BMP STRING";
                            break;
                        default:
                            retval += "UNKNOWN TAG";
                            break;
                    };
                }
                return retval;
            }

#if UNITYIAP_DISABLED
        /// <summary>
        /// Read registry information from local machine entrys.
        /// </summary>
        /// <param name="path"></param>
        /// <param name="name"></param>
        /// <returns></returns>
        static public object ReadRegInfo(string path, string name)
        {
            object retval = null;
            Microsoft.Win32.RegistryKey regKey;
            regKey = Registry.LocalMachine.OpenSubKey(path, false);
            if (regKey!=null)
            {
                retval = regKey.GetValue(name);
            }
            return retval;
        }

        /// <summary>
        /// Write information into local machine registry entry.
        /// </summary>
        /// <param name="path"></param>
        /// <param name="name"></param>
        /// <param name="data"></param>
        static public void WriteRegInfo(string path, string name, object data)
        {
            Microsoft.Win32.RegistryKey regKey;
            regKey = Registry.LocalMachine.OpenSubKey(path, true);
            if (regKey == null)
            {
                regKey = Registry.LocalMachine.CreateSubKey(path);
            }
            if (regKey != null)
            {
                regKey.SetValue(name, data);
            }
        }
#endif

            /// <summary>
            /// Constructor.
            /// </summary>
            private Asn1Util()
            {
                //Private constructor.
            }

        }
    }
}